package cn.tpshion.config.support;

import cn.tpshion.config.common.AssertUtil;
import cn.tpshion.config.service.ConfigAuthService;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.apache.curator.framework.recipes.cache.CuratorCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

public class MySpringApplicationRunListener implements SpringApplicationRunListener {

    private static final Logger log = LoggerFactory.getLogger(MySpringApplicationRunListener.class);

    private final SpringApplication application;

    private final String[] args;

    private String host;

    private String name;

    private String path;

    private static CuratorFramework zkClient;

    private static final String ZOOKEEPER_CONFIG = "zookeeperConfig";

    private ConfigurableEnvironment environment;

    private Map<String,String> map = new ConcurrentHashMap<>();

    private Map<String,RefreshBean> refreshMap = new ConcurrentHashMap<>();

    private static Properties properties = new Properties();

    public MySpringApplicationRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        this.environment = environment;
        String enable = environment.getProperty("config.enable");
        if(StringUtils.hasText(enable) && "true".equals(enable)){
            ConfigAuthService configAuthService = new ConfigAuthService();
            configAuthService.setEnvironment(environment);
            String token = configAuthService.auth();

            host = configAuthService.getConfigHost(token);

            name = environment.getProperty("config.name");
            AssertUtil.notEmpty(name,"config.name配置不能为空");

            log.info("初始化配置中心，host:{} name:{}", this.host, this.name);
            try {
                // 因为zookeeper以节点的方式获取数据，因此需要加上“/”
                path = "/" + this.name;
                RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
                zkClient = CuratorFrameworkFactory.builder()
                        .connectString(this.host)
                        .sessionTimeoutMs(60 * 1000)
                        .connectionTimeoutMs(15 * 1000)
                        .retryPolicy(retryPolicy)
                        .namespace("config")
                        .build();
                zkClient.start();
                log.info("配置中心初始化完成");
                // 判断配置是否存在
                Stat stat = zkClient.checkExists().forPath(path);
                if(Objects.isNull(stat)){
                    log.error("{} 配置不存在，请先在配置中心完成该配置内容！", this.name);
                    throw new ConfigException(this.name + " 配置不存在，请先在配置中心完成该配置内容！");
                }else{
                    addConfigToSpring();
                }
                configListener();
            }catch (Exception e){
                log.error("配置拉取失败：",e.getMessage(),e);
                throw new ConfigException("配置拉取失败");
            }
        }
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = context.getBean(beanDefinitionName);
            Field[] fields = bean.getClass().getDeclaredFields();
            for (Field field : fields) {
                Value annotation = field.getAnnotation(Value.class);
                if(annotation != null){
                    RefreshBean refreshBean = new RefreshBean();
                    refreshBean.setBean(bean);
                    refreshBean.setBeanName(beanDefinitionName);
                    refreshBean.setFieldName(field.getName());
                    refreshMap.put(annotation.value(),refreshBean);
                }
            }
        }
        log.info("refreshMap:{}",refreshMap);
    }

    /**
     * 刷新bean的@Value注解中的配置内容
     * @param key 配置名称
     * @param value 配置内容
     */
    private void refreshBean(String key, String value){
        refreshMap.forEach((k,v) -> {
            if(k.contains(key)){
                log.debug("修改@Valule配置内容：key={} value={}",k,value);
                try {
                    Field field = v.getBean().getClass().getDeclaredField(v.getFieldName());
                    field.setAccessible(true);
                    field.set(v.getBean(),value);
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    log.error("更新配置失败：{}",e.getMessage(),e);
                    throw new ConfigException("更新配置失败");
                }
            }
        });
    }

    /**
     * 添加远程配置到spring环境中
     * @throws Exception
     */
    public void addConfigToSpring() throws Exception{
        // 检查spring中是否存在配置域
        if(!checkExistsEnv()){
            createZookeeperConfig();
        }
        // 远程配置放置到spring环境中
        PropertySource<?> propertySource = environment.getPropertySources().get(ZOOKEEPER_CONFIG);
        ConcurrentHashMap<String,String> zkMap = (ConcurrentHashMap<String,String>) propertySource.getSource();
        // 获取zookeeper的配置
        List<String> list = zkClient.getChildren().forPath(path);
        for (String key : list) {
            String data = new String(zkClient.getData().forPath(path + "/" + key));
            log.debug("拉取配置中心配置: 配置域={} key={} value={}",path,key, data);
            zkMap.put(key, data);
            properties.put(key, data);
        }
    }

    /**
     * 监听远程配置
     */
    private void configListener(){
        CuratorCache curatorCache = CuratorCache.build(zkClient,path);
        CuratorCacheListener curatorCacheListener = new CuratorCacheListener() {
            @Override
            public void event(Type type, ChildData oldData, ChildData data) {
                if(Type.NODE_CREATED.equals(type)){
                    String childPath = data.getPath();
                    if(path.equalsIgnoreCase(childPath)){
                        return;
                    }
                    String key = childPath.substring(path.length() + 1);
                    String value = new String(data.getData());
                    log.info("配置中心新增配置: 配置域={} 节点={} key={} value={}",name,childPath,key,value);
                    getZkMap().put(key, value);
                    properties.put(key,value);
                    refreshBean(key,value);
                }
                if(Type.NODE_CHANGED.equals(type)){
                    String childPath = data.getPath();
                    if(path.equalsIgnoreCase(childPath)){
                        return;
                    }
                    String key = path.equalsIgnoreCase(childPath) ? childPath.substring(1) : childPath.substring(path.length() + 1);
                    String value = new String(data.getData());
                    log.info("配置中心修改配置: 配置域={} 节点={} key={} value={}",name,childPath,key,value);
                    getZkMap().put(key, value);
                    properties.put(key,value);
                    refreshBean(key,value);
                }
                if(Type.NODE_DELETED.equals(type)){
                    String childPath = oldData.getPath();
                    if(path.equalsIgnoreCase(childPath)){
                        log.warn("注意！配置中心删除域配置：{} 请关注应用配置状况。",childPath);
                        return;
                    }
                    String key = path.equalsIgnoreCase(childPath) ? childPath.substring(1) : childPath.substring(path.length() + 1);
                    String value = new String(oldData.getData());
                    log.info("配置中心删除配置: 配置域={} 节点={} key={} value={}",name,childPath,key,value);
                    getZkMap().remove(key);
                    properties.remove(key);
                }
            }
        };

        curatorCache.listenable().addListener(curatorCacheListener);
        curatorCache.start();
    }

    /**
     * 获取zookeeper的配置容器
     * @return
     */
    private ConcurrentHashMap<String,String> getZkMap(){
        PropertySource<?> propertySource = environment.getPropertySources().get(ZOOKEEPER_CONFIG);
        ConcurrentHashMap<String,String> zkMap = (ConcurrentHashMap<String,String>) propertySource.getSource();
        return zkMap;
    }

    /**
     * 创建配置域
     */
    private void createZookeeperConfig(){
        MutablePropertySources propertySources = environment.getPropertySources();
        OriginTrackedMapPropertySource zkPropertySource = new OriginTrackedMapPropertySource(ZOOKEEPER_CONFIG, map);
        propertySources.addLast(zkPropertySource);
    }

    /**
     * 检查spring中配置域是否存在
     * @return
     */
    private boolean checkExistsEnv(){
        MutablePropertySources propertySources = environment.getPropertySources();
        for (PropertySource<?> propertySource : propertySources) {
            if(ZOOKEEPER_CONFIG.equalsIgnoreCase(propertySource.getName())){
                return true;
            }
        }
        return false;
    }

    public static CuratorFramework getZkClient(){
        return zkClient;
    }

    public static Properties getProperties(){
        return properties;
    }
}
